テスト駆動開発 (Beck)
https://m.media-amazon.com/images/I/51qh3suMX0L._SY346_.jpg
(2020-05 読了)
感想 nobuoka.icon
和田さんが最後に書いている 「テストで質は上がらない。 TDD における質の向上の手段はリファクタリングによる継続的でインクリメンタルな設計である」 という話が良かった TDD 初心者の人は是非読むと良い
I 部と II 部は、どういう風に TDD を進めていくか、実例を交えて説明してもらえる
TDD に慣れてなくて教えてくれる人もいない状況だとかなり役に立つと思う
初心者じゃない人も、まえがきや III 部で学ぶことはある
自分にとってはまえがきと III 部が良かった
TDD の目的や歴史的な経緯とかが学べた
BDD と TDD の関係についていまいちちゃんと理解してなかったので、そこらへんも学べた
原著は 15 年以上前の古い本ではあるが、TDD の本質的なことが書かれているので今でも十分通用する内容
和田さんによるコードの書き直しや、最近の TDD 関連の状況についての章の追加もあるので、そういう意味でも古さで敬遠しなくて良い
海外の技術書にありがちだと思うけど、大袈裟な言い回しとかが面白い (「リファクタリングしないとあなたの歯はボロボロになる、みたいな」)
内容まとめ
まえがき
動作するきれいなコードの価値
開発が予測可能になり、バグが残っているかを心配する必要もない
コードの意図が明確になる
ソフトウェアのユーザを快適にする
チームメンバー同士信頼しあえる
書いていて気持ちがいい
TDD のシンプルなルール
自動化されたテストが失敗した時のみ新しいコードを書く
重複を除去する
上記のルールが個人とグループに与える影響
動作するコードが設計判断にフィードバックをもたらすことで、有機的に設計を進められるように
自分たちでテストを書くようになる
小さな変更に迅速に応答する開発環境を備えるように
凝集度が高く、結合度が低いたくさんの部品で構成された設計を行うように (その方がテストが書きやすいから)
TDD のマントラ : レッド・グリーン・リファクタリング
レッド : 通らないテストを書く
グリーン : テストを通す
リファクタリング : 重複を除去する
勇気 : TDD はプログラミング中の不安をコントロールする手法
不安 : 困難な問題を最初から全て見通せないという真っ当な感覚
TDD はプログラミング中の設計判断とフィードバックの間にあるギャップを認識し、そのギャップをコントロールする技法でもある
I 部 多国通貨
リファクタリングが無ければ設計は次第に腐り、あなたは職や家族を失い、健康に気をつかわなくなり、虫歯になる。 つまり、歯の健康のためには、遡ってテストをリファクタリングの前に書かなければならないわけだ。
テスト駆動で開発されたコードの欠陥密度が十分に低い場合には、プロのテストエンジニアの役割は 「品質管理のセンセイ」 から、システムに関わる人どうし、特に要求を出す人と実際に開発する人のコミュニケーションを加速させる役割に必然的に変わる。
2 つのテスト評価手法
欠陥挿入 (defect insertion) : プロダクトコードの任意の行の意味合いを変えたら、テストは失敗しなければならない カバレッジとは、プログラムを様々な側面から検証しているテストの数を、テストすべき側面の数 (ロジックの複雑度) で割ったもの
なので、テストが増えればカバレッジは上がる
ロジックを単純化することでもカバレッジは上がる
II 部 xUnit
Arrange : オブジェクトの準備
Act : 操作の実行
Assert : 結果の検証
テスト間には絶対に依存関係を作ってはならない
実行順でテストが失敗したり成功したりする
Python はオブジェクト指向サポートを後から付け加えられた言語
そうなのか?? nobuoka.icon
自分自身で xUnit を実装してみる意義
熟達 : xUnit そのものの理解のため
xUnit はシンプルではある
Martin Fowler はそれを 「ソフトウェアエンジニアリングの歴史の中で、かくも多くの人間が、かくも大きな恩恵を、かくも少ないコードによって受けたことはいまだかつてなかった」 と表現している それでも複雑な部分はある
探索 : 新しいプログラミング言語を学ぶ
III 部 テスト駆動開発のパターン
25 章 テスト駆動開発のパターン
テストの意味
テストは 「評価する」 という意味の動詞
合格か不合格かを判定する手順を意味する名詞でもある
「評価する」 というのと、判定手順が定まっているのでは、だいぶ印象が違うのではないか?
ストレスがかかっている状態だとテストを十分に行わず、テストが十分に行われていないとエラーが増えてストレスが増える、というループ
このテストを 「自動テスト」 に置き換えるとこの憂鬱なループを断ち切れる
何をテストすべきかは最初に書き出しておく
テストするべきものの概要をリストとして書いておくのがオススメ
テスト自体を最初に全部書くのはおすすめではない
常に一つの変更を行う、というのを順番にやっていくこと
慎重な登山者の間では、両手両足の合わせて 4 本のうち 3 本は陸地に接していなければならないというルールがあるそうだ。 大胆にも 2 つ同時に動かすと、危険が大幅にますというわけだ。
26 章 レッドバーのパターン
休憩
疲れた時は休憩を取ろう
TDD はシャワーメソッドの改良版
まだ正しい実装が見えてこないなら三角測量を行い、それでもまだわからないならシャワーを浴びにいく 27 章 テスティングのパターン
Mock Object
モックを使うと必然的にオブジェクトの可視性に留意するようになり、コードの結合度を低く抑えることにつながる
本物の挙動と異なるリスクはあるが、本物のリソースに対しても同様のテストを走らせることでそのリスクは抑えられる
Self Shunt (自己接続) パターン
テスト対象が別のオブジェクトとやりとりしていることを確かめたい場合
テストケース自身とテスト対象をやりとりさせると、読みやすくなることが多い
Log String (記録用文字列) パターン
正しい順序でメソッド呼び出しが行われていることをテストしたい時
記録用文字列を作り、メソッド呼び出しのたびにその文字列に追記するようにする
Crash Test Dummy (衝突実験ダミー人形) パターン
普通のやり方では到達させるのが難しいエラー処理部分のテストをしたい場合 (例えばディスク容量がなくてファイルが作れない場合、とか)
状況をシミュレートするための仮実装を使う (特定メソッドだけオーバーライドして、その状況で起こる例外を明示的に発生させるとか)
失敗させたままのテスト
独りでプログラミングしている時には、作業中のテストを 1 つ失敗する状態で終わらせておく
続きがどこからやれば良いかがすぐにわかる
28 章 グリーンバーのパターン
とりあえずテストを通すために固定値を返すとか、そういうやつ
2 つの効果
心理的効果 : テストが通っているのといないのとでは精神状態が全く異なる。 テストに通ってさえいれば、そこから自信を持ってリファクタリングできる
スコープ制御 : 1 つの実例から初めて、そこから一般化していくことで、本質と関係ない問題に気を取られずに作業を行えるようになる
テストから最も慎重に一般化を引き出すやり方
2 つ以上の実例の検証をテストに記述して、そのテストに通るように実装を変更する
明白な実装
どういう実装にすべきかわかっているならそのまま書いてしまえば良いが、常にそうするのは難しい
きれいなコードと動くコードの両立は難しいので、順番に
明白な実装だけを使うならば、自分自身が完璧でなければならなくなる。 そんなふうにしていると、衝撃的な結果がもたらされうることが心理学的にわかっている。 いま書いたコードが、テストを通すための最もシンプルな変更ではなかったと気づいたとき、あるいはペアプロのパートナーがもっとシンプルな解を示したとき、自分は負け犬だと気づかされる。 世界は音を立てて崩れ落ち、一瞬で周囲の大気が凍り付き、あなたは死ぬ。
29 章 xUnit のパターン
ホワイトボックステストを書きたくなるのは、テストの問題ではなく設計の問題
プライベートな変数やメソッドを検査して対応してしまうと設計の改善の機会が失われるのでやめろ
フィクスチャー
オブジェクトを作成してテストの事前状態を作り上げる
テストのアウトライン
テストクラスに追加すべきメソッド一覧を、クラスの中のコメントとして記載しておくやり方 (それらのコメントがアウトライン)
本来アウトラインは、テスト対象クラスの契約 (契約による設計における契約) を示すドキュメントへと育つもの 30 章 デザインパターン
パターンに関する洞察として 「解決する問題はそれぞれ全く異なるように思えるが、その問題の多くは使用するツールにより生成されるのであって、外側の問題により生成されるのではない」 (『形の合成に関するノート』 より) というものがある 問題自体が多様で異なる背景を持っていようとも、実は問題は一般的で、解も一般的だと期待できる。 実装にそう
GoF 本は設計をフェーズとして扱うという前提があるように見える
31 章 リファクタリング
一般的には、リファクタリングはプログラムの意味を変えることを許さない
TDD におけるリファクタリングでは、テストが通りさえすれば良い
十分なテストを書く責務が生じる
「問題がありそうなことはわかってたんだけど、テストが全部通ったのでコミットしたんだ」 などという言い訳はできなくなる。 もっとテストを書くんだ。
32 章 TDD を身につける
テストしなくて良いものはあるか?
シンプルな答えは、不安が退屈に変わるまでテストを書く、というもの
どのぐらいテストを書けばよいかについて考えるとき、私は平均故障間隔 (MTBF : Mean Time Between Failures) を思い浮かべる
正確にはバリエーションの発生によって満たされるようになる
バリエーションの導入がはやくなればなるほど、TDD は事前設計と見分けがつかなくなる
テストを消す基準
自信 : システムの振る舞いに対する自信が減るなら消してはならない
コミュニケーション : テストの読み手に、他のテストにはないシナリオを伝えるならば、それは消してはならない
アプリケーションレベルのテスト駆動開発
ATDD (application test-driven development) を行おうとすると、開発サイクルの中の実装が始まるときに顧客がテストを書く必要がある ATDD は、現代では Acceptance Test Driven Development を意味するが、大体同じ意味
レッド・グリーン・リファクタリングのリズムの習得と、技術的課題であるアプリケーションレベルのフィクスチャー作成と、顧客にテストを書いてもらうという組織的変革を一度に起こすのは難しい
途中から TDD に乗り換えるには?
最大の問題は、テストのことを考えずに書かれたコードはテストが書きにくいことが多い
まずは変更のスコープを定めること。 いきなり全部を書き換えるのは無理
テストとリファクタリングの間のデッドロックを解消する
ペアで慎重に作業するとか
やがて、システムのよく変更される部分についてはテスト駆動で開発されているように見え始める
TDD の名前の由来
開発 : 開発は、分析・論理設計・物理設計・実装・テスト・レビュー・結合・デプロイを伴う複雑なダンス
フェーズで区切ると、作業間のフィードバックが難しくなる
駆動 : 「テストファースト開発」 と言っていたこともあるが、多くの開発者はテストを後に書く
反対後にはぼんやりした違和感がなければならないという命名規則で考えると、テストファーストは微妙
「テスト駆動」 だと、反対語を考えても 「テストじゃなければ何で駆動する??」 となって良いっぽい
テスト : 自動化され、具体化された、明確なテスト。 TDD はテスト技法ではなく、分析技法であり、設計技法であり、開発の全ての活動を構造化する技法
付録
系の要素同士がどう影響を与え合うかを解き明かすのが目的
付録 C 訳者解説 : テスト駆動開発の現在
訳者和田さんによる歴史や現状の解説
ソフトウェア開発には 2 つのフィードバックループがある
外側 : 外部品質を高める
内側 : 内部品質を高める
プロジェクトの最初から最低限動くシステム (ウォーキングスケルトン) を作り、そこにインクリメンタルに機能を追加していく方法を示した
モックオブジェクトを活用することで、外側のループから先に作ることができる (開発の向きを制御できる)
クラスや構造ではなく、オブジェクトとやりとりから先に考える設計手法
本書の TDD はひとりでできる開発上の手法であるが、GOOS の TDD はアプリケーションレベル・チームレベルにまで拡大
ソフトウェアテストの世界における本来のテストとは、認知の外を探求する創造的破壊行為
TDD のテストはプログラミングや設計の補助線
TDD の 「テスト」 という言葉のニュアンスの問題に対処するため、「テスト」 以外に良い言葉はないかと考えられたのが振る舞い → BDD テストやアサーションといった語彙を別の言葉に置き換えることで、先入観を避ける
当初は TDD と同じく、GOOS における 2 つのフィードバックループのうち内側の方を対象としていた
table:BDD の語彙
変更前 変更後
assertion expectation
test example
testcase example group
test class spec
外側のフィードバックループについても対応
顧客参加のためには、プログラミング言語で書かれていることと、技術者の語彙で書かれていることの 2 つに対処する必要がある
Arrange, Act, Assert の語彙を Given, When, Then に変更
BDD とは、ステークホルダーの視点に立って振る舞いを説明することにより、アプリケーションを実装するための手法である
受け入れテストを媒体としてアジャイル開発の計画を行うプラクティスを、一部では ATDP (Acceptance Test-Driven Planning) と呼ばれていた 本来の TDD は、あくまでプログラマにとっての開発の補助線だった
GOOS や BDD を経て拡大していくことで、2 つの変化が訪れた
教条主義化
TDD/BDD をやっていれば安心、と考える人や、「正しく」 TDD/BDD を実践しない人を非難するような人も
TDD はテストだという誤解を解くために BDD が発明されたが、それが今やテストだと認識されている
BDD の内側のループを構成する技術はテスト語彙の方言として残って、テストを書くために使われている
現状の多くの人の認識は以下のような感じだと思われる
TDD : テストを先に書くコードの書き方
モックオブジェクト : テストを書くのが難しい対象に対して無理やりテストを書く際に使うツール
BDD style = xSpec 系の語彙でテストコードを書くこと
BDD = Cucumber のような Gherkin 系ツールを使って受け入れテストを自動化すること
TDD の本質は、精神状態のコントロール、不安と自信の制御にある
結果ではなく過程に本質がある
TDD はスキルである。 才能ではなく修練の話
テストは質を上げない、質を上げるのはプログラミング
テストを書くことが一般的になってきたが、そのテストはプログラミングの質を上げているか?
テストでは質がわかるだけ
テストを書いても設計を改善しないならばそれはただの回帰テストで、現状の追認でしかない
TDD における質の向上の手段はリファクタリングによる継続的でインクリメンタルな設計である